using System;
using System.Collections;
using System.Collections.Generic;
using TestCases.CompareAssert.Core.Comparers.Interfaces;
using TestCases.CompareAssert.Core.Interfaces;

namespace TestCases.CompareAssert.Core.Comparers.Collections
{
    public class EnumerableComparer : IObjectComparer
    {
        public const string IdentifiableName = "EnumerableComparer";
        
        public IObjectComparer ElementComparer { get; set; }
        
        public EnumerableComparer()
        {            
        }
        
        public EnumerableComparer(IObjectComparer comparer)
        {
            ElementComparer = comparer;
        }
        
        public Predicate<long> IgnoreElementAt { get; set; }

        public string Name
        {
            get { return IdentifiableName; }
        }

        public IResultBuilder Compare(ICompareContext context)
        {
            if (!(context.LValue is IEnumerable))
            {
                return this.IsNotApplicable(context).Left.IsNot<IEnumerable>();
            }

            if (!(context.RValue is IEnumerable))
            {
                return this.IsNotApplicable(context).Right.IsNot<IEnumerable>();
            }

            var results = new List<IResultProvider>();

            IEnumerator enumerator1 = ((IEnumerable) context.LValue).GetEnumerator(),
                        enumerator2 = ((IEnumerable) context.RValue).GetEnumerator();

            long index = 0;
            while (true)
            {
                bool firstMoved = enumerator1.MoveNext(), secondMoved = enumerator2.MoveNext();
                if (!firstMoved && !secondMoved)
                {
                    break;
                }

                if (firstMoved && !secondMoved)
                {
                    return this.IsNotEquals(context, results).
                        Message("<{LPath}> has more elements than <{RPath}>. <{RPath}>.Count() is <{Count}>.").
                        With(new {Count = index});
                }

                if (!firstMoved /* && secondMoved*/)
                {
                    return this.IsNotEquals(context, results).
                        Message("<{LPath}> has less elements than <{RPath}>. <{LPath}>.Count() is <{Count}>.").
                        With(new {Count = index});
                }
                
                if (IgnoreElementAt == null || !IgnoreElementAt(index))
                {
                    var indexedContext = context.ForIndexedObjects(enumerator1.Current, enumerator2.Current, index);
                    if (ElementComparer.CompareAndAddResult(indexedContext, results).GetStatus() == CompareStatus.NotEquals)
                    {
                        return this.IsNotEquals(context, results).
                            Message("<{LElementPath}> not equals to <{RElementPath}> [{LElementValue} != {RElementValue}].").
                            With(new
                            {
                                LElementPath = indexedContext.LPath, 
                                RElementPath = indexedContext.RPath, 
                                LElementValue = indexedContext.LValue,
                                RElementValue = indexedContext.RValue
                            });
                    }
                }
                index++;
            }
            return this.IsEquals(context, results).Message("All <{LPath}> and <{RPath}> elements are equals.");
        }
    }
}